﻿global using Microsoft.EntityFrameworkCore;
global using Singer.Shared.Domain;

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Singer.Core;
using Singer.Shared.UserContext;
using System.Linq.Expressions;

namespace Singer.Middleware.EFCore;

/// <summary>
/// DbContext 数据库上下文 基类
/// </summary>
public class EFCoreDbContext : DbContext
{
    private readonly IServiceProvider ServiceProvider;
    private readonly IUserContextAccessor? _userContextAccessor;
    private readonly EFCoreOptions _efoptions;
    public EFCoreDbContext(IServiceProvider serviceProvider, DbContextOptions options) : base(options)
    {
        ServiceProvider = serviceProvider;
        _userContextAccessor = serviceProvider.GetService<IUserContextAccessor>();
        _efoptions = serviceProvider.GetRequiredService<IOptionsSnapshot<EFCoreOptions>>().Value;
        if (_efoptions.UseAopAudit)
            SavingChanges += EFCoreDbContext_SavingChanges;
    }

    private void EFCoreDbContext_SavingChanges(object? sender, SavingChangesEventArgs e)
    {
        IUserContext? _userContext = _userContextAccessor?.UserContext;
        foreach (var entry in ChangeTracker.Entries())
        {
            if (entry.State == EntityState.Added)
            {
                if (entry.Entity is ICreator creator)
                {
                    if (string.IsNullOrWhiteSpace(creator.CreatorId) && !string.IsNullOrWhiteSpace(_userContext?.UserId))
                    {
                        entry.Entity.GetType().GetProperty(nameof(creator.CreatorId))?.SetValue(entry.Entity, _userContext.UserId);
                        entry.Entity.GetType().GetProperty(nameof(creator.CreatorName))?.SetValue(entry.Entity, _userContext.UserName);
                    }
                    if (creator.CreateTime == DateTime.MinValue)
                        entry.Entity.GetType().GetProperty(nameof(creator.CreateTime))?.SetValue(entry.Entity, Cores.BeiJingNow);
                }
            }
            else if (entry.State == EntityState.Modified)
            {
                if (entry.Entity is IChanger changer)
                {
                    if (!string.IsNullOrWhiteSpace(_userContext?.UserId))
                    {
                        entry.Entity.GetType().GetProperty(nameof(changer.ChangerId))?.SetValue(entry.Entity, _userContext.UserId);
                        entry.Entity.GetType().GetProperty(nameof(changer.ChangerName))?.SetValue(entry.Entity, _userContext.UserName);
                    }
                    entry.Entity.GetType().GetProperty(nameof(changer.ChangeTime))?.SetValue(entry.Entity, Cores.BeiJingNow);
                }
            }
            else if (entry.State == EntityState.Deleted)
            {
                if (entry.Entity is ISoftDelete deleter)
                {
                    entry.State = EntityState.Modified;
                    if (!string.IsNullOrWhiteSpace(_userContext?.UserId))
                    {
                        entry.Entity.GetType().GetProperty(nameof(deleter.DeleterId))?.SetValue(entry.Entity, _userContext.UserId);
                        entry.Entity.GetType().GetProperty(nameof(deleter.DeleterName))?.SetValue(entry.Entity, _userContext.UserName);
                    }
                    entry.Entity.GetType().GetProperty(nameof(deleter.IsDeleted))?.SetValue(entry.Entity, true);
                    entry.Entity.GetType().GetProperty(nameof(deleter.DeleteTime))?.SetValue(entry.Entity, Cores.BeiJingNow);
                }
            }

        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        ApplySoftDeleteGlobalFilter(modelBuilder);
    }

    /// <summary>
    /// 过滤掉全部软删除的数据
    /// </summary>
    private void ApplySoftDeleteGlobalFilter(ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
            {
                var parameter = Expression.Parameter(entityType.ClrType, "entity");
                var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
                var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant(nameof(ISoftDelete.IsDeleted)));
                var compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
                var lambda = Expression.Lambda(compareExpression, parameter);
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
            }
        }
    }
}
